--- /dev/null
+/*
+ Read files containing selected NMEA 0183 sentences.
+ Based on information by Eino Uikkanenj
+
+ Copyright (C) 2004-2015 Robert Lipe, robertlipe+source@gpsbabel.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
+
+ */
+
+
+#include "defs.h"
+#include "cet_util.h"
+#include "gbser.h"
+#include "strptime.h"
+#include "jeeps/gpsmath.h"
+
+#include <ctype.h>
+#include <math.h>
+#include <time.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <QtCore/QStringList>
+
+/**********************************************************
+
+ ' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ ' $GPGGA - Global Positioning System Fix Data
+ ' $GPGGA,155537,6006.718,N,02426.290,E,1,05,2.4,50.5,M,19.7,M,,*79
+ ' 2 123519 Fix taken at 12:35:19 UTC
+ ' 3,4 4807.038,N Latitude 48 deg 07.038' N
+ ' 5,6 01131.324,E Longitude 11 deg 31.324' E
+ ' 7 1 Fix quality: 0 = invalid
+ ' 1 = GPS fix
+ ' 2 = DGPS fix
+ ' 8 08 Number of satellites being tracked
+ ' 9 0.9 Horizontal dilution of position
+ ' 10,11 545.4,M Altitude, Metres, above mean sea level
+ ' 12,13 46.9,M Height of geoid (mean sea level) above WGS84 ellipsoid
+ ' 14 (empty field) time in seconds since last DGPS update
+ ' 15 (empty field) DGPS station ID number
+
+ ' $GPWPL - waypoint location
+ ' $GPWPL,4917.16,N,12310.64,W,003*65
+ ' 2,3 4917.16,N Latitude of waypoint
+ ' 4,5 12310.64,W Longitude of waypoint
+ ' 6 003 Waypoint ID
+
+ ' $GPGLL - Geographic position, Latitude and Longitude
+ ' $GPGLL,4916.45,N,12311.12,W,225444,A
+ ' 2,3 4916.46,N Latitude 49 deg. 16.45 min. North
+ ' 4,5 12311.12,W Longitude 123 deg. 11.12 min. West
+ ' 6 225444 Fix taken at 22:54:44 UTC
+ ' 7 A Data valid
+
+ ' $GPRMC - Recommended minimum specific GNSS Data
+ ' $GPRMC,085721.194,A,5917.7210,N,01103.9227,E,21.42,50.33,300504,,*07
+ ' 2 085721 Fix taken at 08:57:21 UTC
+ ' 3 A Fix valid (this field reads V if fix is not valid)
+ ' 4,5 5917.7210,N Latitude 59 deg 17.7210' N
+ ' 6,7 01103.9227,E Longitude 11 deg 03.9227' E
+ ' 8 21.42 Speed over ground (knots)
+ ' 9 50.33 Course over ground (true)
+ ' 10 300504 Date 30/05-2004
+ ' 11 Empty field Magnetic variation
+
+ GSA - GPS DOP and active satellites
+ $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
+ A Auto selection of 2D or 3D fix (M = manual)
+ 3 3D fix
+ 04,05... PRNs of satellites used for fix (space for 12)
+ 2.5 PDOP (dilution of precision)
+ 1.3 Horizontal dilution of precision (HDOP)
+ 2.1 Vertical dilution of precision (VDOP)
+ DOP is an indication of the effect of satellite geometry on
+ the accuracy of the fix.
+
+ VTG - Track made good and ground speed
+ $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K
+ 054.7,T True track made good
+ 034.4,M Magnetic track made good
+ 005.5,N Ground speed, knots
+ 010.2,K Ground speed, Kilometers per hour
+
+ WPL - waypoint location
+ $GPWPL,4917.16,N,12310.64,W,003*65
+ 4917.16,N Latitude of waypoint
+ 12310.64,W Longitude of waypoint
+ 003 Waypoint ID
+ When a route is active, this sentence is sent once for each
+ waypoint in the route, in sequence. When all waypoints have
+ been reported, GPR00 is sent in the next data set. In any
+ group of sentences, only one WPL sentence, or an R00
+ sentence, will be sent.
+
+
+ ' The optional checksum field consists of a "*" and two hex digits repre-
+ ' senting the exclusive OR of all characters between, but not including,
+ ' the "$" and "*". A checksum is required on some sentences.
+
+****************************************/
+
+/*
+ * An input file may have both GGA and GLL and RMC sentences for the exact
+ * same position fix. If we see a single GGA, start ignoring GLL's and RMC's.
+ * GLL's will also be ignored if RMC's are found and GGA's not found.
+ */
+
+/*
+Zmarties notes:
+
+In practice, all fields of the NMEA sentences should be treated as optional -
+if the data is not available, then the field can be omitted (hence leading
+to the output of two consecutive commas).
+
+An NMEA recording can start anywhere in the stream of data. It is therefore
+necessary to discard sentences until sufficient data has been processed to
+have all the necessary data to construct a waypoint. In practice, this means
+discarding data until we have had the first sentence that provides the date.
+(We could scan forwards in the stream of data to find the first date, and
+then back apply it to all previous sentences, but that is probably more
+complexity that is necessary - the lost of one waypoint at the start of the
+stream can normally be tolerated.)
+
+If a sentence is received without a checksum, but previous sentences have
+had checksums, it is best to discard that sentence. In practice, the only
+time I have seen this is when the recording stops suddenly, where the last
+sentence is truncated - and missing part of the line, including the checksum.
+*/
+
+typedef enum {
+ gp_unknown = 0,
+ gpgga,
+ gplgll,
+ gprmc
+} preferred_posn_type;
+
+static enum {
+ rm_unknown = 0,
+ rm_serial,
+ rm_file
+} read_mode;
+
+static gbfile* file_in, *file_out;
+static route_head* trk_head;
+static short_handle mkshort_handle;
+static preferred_posn_type posn_type;
+static struct tm tm;
+static Waypoint* curr_waypt;
+static Waypoint* last_waypt;
+static void* gbser_handle;
+static QString posn_fname;
+static queue pcmpt_head;
+
+static int without_date; /* number of created trackpoints without a valid date */
+static struct tm opt_tm; /* converted "date" parameter */
+
+#define MYNAME "nmea"
+
+static char* opt_gprmc;
+static char* opt_gpgga;
+static char* opt_gpvtg;
+static char* opt_gpgsa;
+static char* snlenopt;
+static char* optdate;
+static char* getposnarg;
+static char* opt_sleep;
+static char* opt_baud;
+static char* opt_append;
+static char* opt_gisteq;
+static char* opt_ignorefix;
+
+static long sleepus;
+static int getposn;
+static int append_output;
+static int amod_waypoint;
+
+static time_t last_time;
+static double last_read_time; /* Last timestamp of GGA or PRMC */
+static int datum;
+static int had_checksum;
+
+static Waypoint* nmea_rd_posn(posn_status*);
+static void nmea_rd_posn_init(const QString& fname);
+
+arglist_t nmea_args[] = {
+ {"snlen", &snlenopt, "Max length of waypoint name to write", "6", ARGTYPE_INT, "1", "64" },
+ {"gprmc", &opt_gprmc, "Read/write GPRMC sentences", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+ {"gpgga", &opt_gpgga, "Read/write GPGGA sentences", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+ {"gpvtg", &opt_gpvtg, "Read/write GPVTG sentences", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+ {"gpgsa", &opt_gpgsa, "Read/write GPGSA sentences", "1", ARGTYPE_BOOL, ARG_NOMINMAX },
+ {"date", &optdate, "Complete date-free tracks with given date (YYYYMMDD).", NULL, ARGTYPE_INT, ARG_NOMINMAX },
+ {
+ "get_posn", &getposnarg, "Return current position as a waypoint",
+ NULL, ARGTYPE_BOOL, ARG_NOMINMAX
+ },
+ {"pause", &opt_sleep, "Decimal seconds to pause between groups of strings", NULL, ARGTYPE_INT, ARG_NOMINMAX },
+ {"append_positioning", &opt_append, "Append realtime positioning data to the output file instead of truncating", "0", ARGTYPE_BOOL, ARG_NOMINMAX },
+ {"baud", &opt_baud, "Speed in bits per second of serial port (baud=4800)", NULL, ARGTYPE_INT, ARG_NOMINMAX },
+ {"gisteq", &opt_gisteq, "Write tracks for Gisteq Phototracker", "0", ARGTYPE_BOOL, ARG_NOMINMAX },
+ {"ignore_fix", &opt_ignorefix, "Accept position fixes in gpgga marked invalid", "0", ARGTYPE_BOOL, ARG_NOMINMAX },
+ ARG_TERMINATOR
+};
+
+#define CHECK_BOOL(a) if (a && (*a == '0')) a = NULL
+
+/*
+ * Slightly different than the Magellan checksum fn.
+ */
+int
+nmea_cksum(const char* const buf)
+{
+ int x = 0 ;
+ const char* p;
+
+ for (p = buf; *p; p++) {
+ x ^= *p;
+ }
+ return x;
+}
+
+static void
+nmea_add_wpt(Waypoint* wpt, route_head* trk)
+{
+ if (datum != DATUM_WGS84) {
+ double lat, lon, alt;
+ GPS_Math_Known_Datum_To_WGS84_M(
+ wpt->latitude, wpt->longitude, 0,
+ &lat, &lon, &alt, datum);
+ wpt->latitude = lat;
+ wpt->longitude = lon;
+ }
+ if (trk != NULL) {
+ track_add_wpt(trk, wpt);
+ } else {
+ waypt_add(wpt);
+ }
+}
+
+static void
+nmea_release_wpt(Waypoint* wpt)
+{
+ if (wpt && ((wpt->Q.next == NULL) || (wpt->Q.next == &wpt->Q))) {
+ /* This waypoint isn't queued.
+ Release it, because we don't have any reference to this
+ waypoint (! memory leak !) */
+ delete wpt;
+ }
+}
+
+static void
+nmea_rd_init(const QString& fname)
+{
+ curr_waypt = NULL;
+ last_waypt = NULL;
+ last_time = -1;
+ datum = DATUM_WGS84;
+ had_checksum = 0;
+
+ CHECK_BOOL(opt_gprmc);
+ CHECK_BOOL(opt_gpgga);
+ CHECK_BOOL(opt_gpvtg);
+ CHECK_BOOL(opt_gpgsa);
+ CHECK_BOOL(opt_gisteq);
+
+ QUEUE_INIT(&pcmpt_head);
+
+ if (getposnarg) {
+ getposn = 1;
+ }
+
+ /* A special case hack that gets our current position and returns
+ * it as one waypoint.
+ */
+ if (getposn) {
+ Waypoint* wpt;
+ posn_status st;
+ nmea_rd_posn_init(fname);
+ wpt = nmea_rd_posn(&st);
+ if (!wpt) {
+ return;
+ }
+ wpt->shortname = "Position";
+ nmea_add_wpt(wpt, NULL);
+ return;
+ }
+
+ read_mode = rm_file;
+ file_in = gbfopen(fname, "rb", MYNAME);
+}
+
+static void
+nmea_rd_deinit(void)
+{
+ switch (read_mode) {
+ case rm_serial:
+ gbser_deinit(gbser_handle);
+ break;
+ case rm_file:
+ gbfclose(file_in);
+ file_in = NULL;
+ break;
+ default:
+ fatal("nmea_rd_deinit: illegal read_mode.\n");
+ break;
+ }
+
+ posn_fname.clear();
+
+}
+
+static void
+nmea_wr_init(const QString& portname)
+{
+ CHECK_BOOL(opt_gprmc);
+ CHECK_BOOL(opt_gpgga);
+ CHECK_BOOL(opt_gpvtg);
+ CHECK_BOOL(opt_gpgsa);
+ CHECK_BOOL(opt_gisteq);
+
+ append_output = atoi(opt_append);
+
+ file_out = gbfopen(portname, append_output ? "a+" : "w+", MYNAME);
+
+ sleepus = -1;
+ if (opt_sleep) {
+ if (*opt_sleep) {
+ sleepus = 1e6 * atof(opt_sleep);
+ } else {
+ sleepus = -1;
+ }
+ }
+
+ mkshort_handle = mkshort_new_handle();
+ setshort_length(mkshort_handle, atoi(snlenopt));
+
+ if (opt_gisteq) {
+ opt_gpgga = NULL;
+ opt_gpvtg = NULL;
+ opt_gpgsa = NULL;
+ }
+}
+
+static void
+nmea_wr_deinit(void)
+{
+ gbfclose(file_out);
+ mkshort_del_handle(&mkshort_handle);
+}
+
+static void
+nmea_set_waypoint_time(Waypoint* wpt, struct tm* time, double fsec)
+{
+ if (time->tm_year == 0) {
+ wpt->SetCreationTime(((((time_t)time->tm_hour * 60) + time->tm_min) * 60) + time->tm_sec, lround(1000.0 * fsec));
+ if (wpt->wpt_flags.fmt_use == 0) {
+ wpt->wpt_flags.fmt_use = 1;
+ without_date++;
+ }
+ } else {
+ wpt->SetCreationTime(mkgmtime(time), lround(1000.0 * fsec));
+ if (wpt->wpt_flags.fmt_use != 0) {
+ wpt->wpt_flags.fmt_use = 0;
+ without_date--;
+ }
+ }
+}
+
+static void
+gpgll_parse(char* ibuf)
+{
+ if (trk_head == NULL) {
+ trk_head = route_head_alloc();
+ track_add_head(trk_head);
+ }
+
+ QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
+
+ double latdeg = 0;
+ if (fields.size() > 1) latdeg = fields[1].toDouble();
+ QChar latdir = 'N';
+ if (fields.size() > 2) latdir = fields[2][0];
+ double lngdeg = 0;
+ if (fields.size() > 3) lngdeg = fields[3].toDouble();
+ QChar lngdir = 'E';
+ if (fields.size() > 4) lngdir = fields[4][0];
+ double hmsd = 0;
+ if (fields.size() > 5) hmsd = fields[5].toDouble();
+ bool valid = false;
+ if (fields.size() > 6) valid = fields[6].startsWith('A');
+
+ if (!valid) {
+ return;
+ }
+
+ int hms = (int) hmsd;
+ last_read_time = hms;
+ double fsec = hmsd - hms;
+
+ tm.tm_sec = hms % 100;
+ hms = hms / 100;
+ tm.tm_min = hms % 100;
+ hms = hms / 100;
+ tm.tm_hour = hms % 100;
+
+ Waypoint* waypt = new Waypoint;
+
+ nmea_set_waypoint_time(waypt, &tm, fsec);
+
+ if (latdir == 'S') {
+ latdeg = -latdeg;
+ }
+ waypt->latitude = ddmm2degrees(latdeg);
+
+ if (lngdir == 'W') {
+ lngdeg = -lngdeg;
+ }
+ waypt->longitude = ddmm2degrees(lngdeg);
+
+ nmea_release_wpt(curr_waypt);
+ curr_waypt = waypt;
+}
+
+static void
+gpgga_parse(char* ibuf)
+{
+ if (trk_head == NULL) {
+ trk_head = route_head_alloc();
+ track_add_head(trk_head);
+ }
+
+ QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
+ double hms = 0;
+ if (fields.size() > 1) hms = fields[1].toDouble();
+ double latdeg = 0;
+ if (fields.size() > 2) latdeg = fields[2].toDouble();
+ QChar latdir = 'N';
+ if (fields.size() > 3) latdir = fields[3][0];
+ double lngdeg = 0;
+ if (fields.size() > 4) lngdeg = fields[4].toDouble();
+ QChar lngdir = 'W';
+ if (fields.size() > 5) lngdir = fields[5][0];
+ int fix = fix_unknown;
+ if (fields.size() > 6) fix = fields[6].toInt();
+ int nsats = 0;
+ if (fields.size() > 7) nsats = fields[7].toInt();
+ double hdop = 0;
+ if (fields.size() > 8) hdop = fields[8].toDouble();
+ double alt = unknown_alt;
+ if (fields.size() > 9) alt = fields[9].toDouble();
+ QChar altunits;
+ if (fields.size() > 10) altunits = fields[10][0];
+ double geoidheight = unknown_alt;
+ if (fields.size() > 11) geoidheight = fields[11].toDouble();
+ QChar geoidheightunits = 'M';
+ if (fields.size() > 12) geoidheightunits = fields[12][0];
+
+ /*
+ * In serial mode, allow the fix with an invalid position through
+ * as serial units will often spit a remembered position up and
+ * that is more comfortable than nothing at all...
+ */
+ CHECK_BOOL(opt_ignorefix);
+ if ((fix <= 0) && (read_mode != rm_serial) && (!opt_ignorefix)) {
+ return;
+ }
+
+ last_read_time = hms;
+ double fsec = hms - (int)hms;
+
+ tm.tm_sec = (long) hms % 100;
+ hms = hms / 100;
+ tm.tm_min = (long) hms % 100;
+ hms = hms / 100;
+ tm.tm_hour = (long) hms % 100;
+
+ Waypoint* waypt = new Waypoint;
+
+ nmea_set_waypoint_time(waypt, &tm, fsec);
+
+ if (latdir == 'S') {
+ latdeg = -latdeg;
+ }
+ waypt->latitude = ddmm2degrees(latdeg);
+
+ if (lngdir == 'W') {
+ lngdeg = -lngdeg;
+ }
+ waypt->longitude = ddmm2degrees(lngdeg);
+
+ waypt->altitude = alt;
+
+ WAYPT_SET(waypt, geoidheight, geoidheight);
+
+ waypt->sat = nsats;
+
+ waypt->hdop = hdop;
+
+ switch (fix) {
+ case 0:
+ waypt->fix = fix_none;
+ break;
+ case 1:
+ waypt->fix = (nsats>3)?(fix_3d):(fix_2d);
+ break;
+ case 2:
+ waypt->fix = fix_dgps;
+ break;
+ case 3:
+ waypt->fix = fix_pps;
+ break;
+ }
+
+ nmea_release_wpt(curr_waypt);
+ curr_waypt = waypt;
+}
+
+static void
+gprmc_parse(char* ibuf)
+{
+ if (trk_head == NULL) {
+ trk_head = route_head_alloc();
+ track_add_head(trk_head);
+ }
+
+ QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
+ double hms = 0;
+ if (fields.size() > 1) hms = fields[1].toDouble();
+ QChar fix = 'V'; // V == "Invalid"
+ if (fields.size() > 2) fix = fields[2][0];
+ double latdeg = 0;
+ if (fields.size() > 3) latdeg = fields[3].toDouble();
+ QChar latdir = 'N';
+ if (fields.size() > 4) latdir = fields[4][0];
+ double lngdeg = 0;
+ if (fields.size() > 5) lngdeg = fields[5].toDouble();
+ QChar lngdir = 'W';
+ if (fields.size() > 6) lngdir = fields[6][0];
+ double speed = 0;
+ if (fields.size() > 7) speed = fields[7].toDouble();
+ double course = 0;
+ if (fields.size() > 8) course = fields[8].toDouble();
+ int dmy = 0;
+ if (fields.size() > 9) dmy = fields[9].toDouble();
+
+ if (fix != 'A') {
+ /* ignore this fix - it is invalid */
+ return;
+ }
+
+ last_read_time = hms;
+ double fsec = hms - (int)hms;
+
+ tm.tm_sec = (long) hms % 100;
+ hms = hms / 100;
+ tm.tm_min = (long) hms % 100;
+ hms = hms / 100;
+ tm.tm_hour = (long) hms % 100;
+
+ tm.tm_year = dmy % 100 + 100;
+ dmy = dmy / 100;
+ tm.tm_mon = dmy % 100 - 1;
+ dmy = dmy / 100;
+ tm.tm_mday = dmy;
+
+ if (posn_type == gpgga) {
+ /* capture useful data update and exit */
+ if (curr_waypt) {
+ if (! WAYPT_HAS(curr_waypt, speed)) {
+ WAYPT_SET(curr_waypt, speed, KNOTS_TO_MPS(speed));
+ }
+ if (! WAYPT_HAS(curr_waypt, course)) {
+ WAYPT_SET(curr_waypt, course, course);
+ }
+ /* The change of date wasn't recorded when
+ * going from 235959 to 000000. */
+ nmea_set_waypoint_time(curr_waypt, &tm, fsec);
+ }
+ /* This point is both a waypoint and a trackpoint. */
+ if (amod_waypoint) {
+ waypt_add(new Waypoint(*curr_waypt));
+ amod_waypoint = 0;
+ }
+ return;
+ }
+
+ Waypoint* waypt = new Waypoint;
+
+ WAYPT_SET(waypt, speed, KNOTS_TO_MPS(speed));
+ WAYPT_SET(waypt, course, course);
+
+ nmea_set_waypoint_time(waypt, &tm, fsec);
+
+ if (latdir == 'S') {
+ latdeg = -latdeg;
+ }
+ waypt->latitude = ddmm2degrees(latdeg);
+
+ if (lngdir == 'W') {
+ lngdeg = -lngdeg;
+ }
+ waypt->longitude = ddmm2degrees(lngdeg);
+
+ nmea_release_wpt(curr_waypt);
+ curr_waypt = waypt;
+
+ /* This point is both a waypoint and a trackpoint. */
+ if (amod_waypoint) {
+ waypt_add(new Waypoint(*waypt));
+ amod_waypoint = 0;
+ }
+}
+
+static void
+gpwpl_parse(char* ibuf)
+{
+ // The last field isn't actually separated by a field separator and
+ // is a string, so we brutally whack the checksum (trailing *NN).
+ QString qibuf = QString(ibuf);
+ qibuf.truncate(qibuf.lastIndexOf('*'));
+ QStringList fields = qibuf.split(",", QString::KeepEmptyParts);
+
+ double latdeg = 0;
+ if (fields.size() > 1) latdeg = fields[1].toDouble();
+ QChar latdir = 'N';
+ if (fields.size() > 2) latdir = fields[2][0];
+ double lngdeg = 0;
+ if (fields.size() > 3) lngdeg = fields[3].toDouble();
+ QChar lngdir = 'E';
+ if (fields.size() > 4) lngdir = fields[4][0];
+ QString sname;
+ if (fields.size() > 5) sname = fields[5];
+
+ if (latdir == 'S') {
+ latdeg = -latdeg;
+ }
+ if (lngdir == 'W') {
+ lngdeg = -lngdeg;
+ }
+
+ Waypoint* waypt = new Waypoint;
+ waypt->latitude = ddmm2degrees(latdeg);
+ waypt->longitude = ddmm2degrees(lngdeg);
+ waypt->shortname = sname;
+
+ curr_waypt = NULL; /* waypoints won't be updated with GPS fixes */
+ nmea_add_wpt(waypt, NULL);
+}
+
+static void
+gpzda_parse(char* ibuf)
+{
+ double hms;
+ int dd, mm, yy, lclhrs, lclmins;
+
+ sscanf(ibuf,"$%*2cZDA,%lf,%d,%d,%d,%d,%d",
+ &hms, &dd, &mm, &yy, &lclhrs, &lclmins);
+ tm.tm_sec = (int) hms % 100;
+ tm.tm_min = (((int) hms - tm.tm_sec) / 100) % 100;
+ tm.tm_hour = (int) hms / 10000;
+ tm.tm_mday = dd;
+ tm.tm_mon = mm - 1;
+ tm.tm_year = yy - 1900;
+ // FIXME: why do we do all this and then do nothing with the result?
+ // This can't have worked.
+}
+
+static void
+gpgsa_parse(char* ibuf)
+{
+ char fixauto;
+ char fix;
+ int prn[12] = {0};
+ int scn,cnt;
+ float pdop=0,hdop=0,vdop=0;
+ char* tok=0;
+
+ memset(prn,0xff,sizeof(prn));
+#if 0
+ scn = sscanf(ibuf,"$%*2cGSA,%c,%c,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+ &fixauto, &fix,
+ &prn[0],&prn[1],&prn[2],&prn[3],&prn[4],&prn[5],
+ &prn[6],&prn[7],&prn[8],&prn[9],&prn[10],&prn[11]);
+ if (scn < 2) {
+ warning(MYNAME ": Short GSA sentence.\n");
+ }
+ /*
+ sscanf has scanned all the leftmost elements
+ we'll rescan by skipping 15 commas to the dops
+ */
+ tok = ibuf;
+ for (cnt=0; (tok)&&(cnt<15); cnt++) {
+ tok = strchr(tok,',');
+ if (!tok) {
+ break;
+ }
+ tok++;
+ }
+ if (tok) {
+ sscanf(tok,"%f,%f,%f",&pdop,&hdop,&vdop);
+ }
+#else
+ QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
+ if (fields.size() > 1)
+ if (fields[1] == 3) {
+ curr_waypt->fix = fix_3d;
+ } else {
+ curr_waypt->fix = fix_2d;
+ }
+ curr_waypt->pdop = pdop;
+ curr_waypt->hdop = hdop;
+ curr_waypt->vdop = vdop;
+#endif
+#if 0
+ if (curr_waypt) {
+
+ if (curr_waypt->fix!=fix_dgps) {
+ if (fix=='3') {
+ curr_waypt->fix=fix_3d;
+ } else if (fix=='2') {
+ curr_waypt->fix=fix_2d;
+ }
+ }
+
+ curr_waypt->pdop = pdop;
+ curr_waypt->hdop = hdop;
+ curr_waypt->vdop = vdop;
+
+ if (curr_waypt->sat <= 0) {
+ for (cnt=0; cnt<12; cnt++) {
+ curr_waypt->sat += (prn[cnt]>0)?(1):(0);
+ }
+ }
+ }
+#endif
+}
+
+static void
+gpvtg_parse(char* ibuf)
+{
+ QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts);
+ double course = 0;
+ if (fields.size() > 1) course = fields[1].toDouble();
+ double speed_n = 0;
+ if (fields.size() > 5) speed_n = fields[5].toDouble();
+ double speed_k = 0;
+ if (fields.size() > 7) speed_k = fields[7].toDouble();
+
+ if (curr_waypt) {
+ WAYPT_SET(curr_waypt, course, course);
+ if (speed_k > 0) {
+ WAYPT_SET(curr_waypt, speed, KPH_TO_MPS(speed_k))
+ } else {
+ WAYPT_SET(curr_waypt, speed, KNOTS_TO_MPS(speed_n));
+ }
+ }
+
+}
+
+/*
+ * AVMAP EKP-IV Tracks - a proprietary (and very weird) extended NMEA.
+ * https://sourceforge.net/tracker/?func=detail&atid=489478&aid=1640814&group_id=58972
+ */
+static
+double pcmpt_deg(int d)
+{
+ int deg = d / 100000;
+ double minutes = (((d / 100000.0) - deg) * 100) / 60.0;
+ return (double) deg + minutes;
+}
+
+void
+pcmpt_parse(char* ibuf)
+{
+ int i, j1, j2, j3, j4, j5, j6;
+ int lat, lon;
+ char altflag, u1, u2;
+ float alt, f1, f2;
+ char coords[20] = {0};
+ int dmy, hms;
+
+ dmy = hms = 0;
+
+ sscanf(ibuf,"$PCMPT,%d,%d,%d,%c,%f,%d,%19[^,],%d,%f,%d,%f,%c,%d,%c,%d",
+ &j1, &j2, &j3, &altflag, &alt, &j4, (char*) &coords,
+ &j5, &f1, &j6, &f2, &u1, &dmy, &u2, &hms);
+
+ if (altflag == 'D' && curr_waypt && alt > 0) {
+ curr_waypt->altitude = alt /*+ 500*/;
+ return;
+ }
+
+ /*
+ * There are a couple of different second line records, but we
+ * don't care about them.
+ */
+ if (j2 != 1) {
+ return;
+ }
+
+ sscanf(coords, "%d%n", &lat, &i);
+ if (coords[i] == 'S') {
+ lat = -lat;
+ }
+ sscanf(coords + i + 1, "%d%n", &lon, &i);
+ if (coords[i] == 'W') {
+ lon= -lon;
+ }
+
+ if (lat || lon) {
+ curr_waypt = new Waypoint;
+ curr_waypt->longitude = pcmpt_deg(lon);
+ curr_waypt->latitude = pcmpt_deg(lat);
+
+ tm.tm_sec = (long) hms % 100;
+ hms = hms / 100;
+ tm.tm_min = (long) hms % 100;
+ hms = hms / 100;
+ tm.tm_hour = (long) hms % 100;
+
+ tm.tm_year = dmy % 10000 - 1900;
+ dmy = dmy / 10000;
+ tm.tm_mon = dmy % 100 - 1;
+ dmy = dmy / 100;
+ tm.tm_mday = dmy;
+ nmea_set_waypoint_time(curr_waypt, &tm, 0);
+ ENQUEUE_HEAD(&pcmpt_head, &curr_waypt->Q);
+ } else {
+ queue* elem, *tmp;
+ route_head* trk_head;
+
+ if (QUEUE_EMPTY(&pcmpt_head)) {
+ return;
+ }
+
+ /*
+ * Since we oh-so-cleverly inserted points at the head,
+ * we can rip through the queue forward now to get our
+ ` * handy-dandy reversing effect.
+ */
+ trk_head = route_head_alloc();
+ track_add_head(trk_head);
+ QUEUE_FOR_EACH(&pcmpt_head, elem, tmp) {
+ Waypoint* wpt = (Waypoint*) dequeue(elem);
+ nmea_add_wpt(wpt, trk_head);
+ }
+ }
+}
+
+static void
+nmea_fix_timestamps(route_head* track)
+{
+ if ((trk_head == NULL) || (without_date == 0)) {
+ return;
+ }
+
+ if (tm.tm_year == 0) {
+ queue* elem, *temp;
+ Waypoint* prev = NULL;
+ time_t delta_tm;
+
+ if (optdate == NULL) {
+ warning(MYNAME ": No date found within track (all points dropped)!\n");
+ warning(MYNAME ": Please use option \"date\" to preset a valid date for thoose tracks.\n");
+ track_del_head(track);
+ return;
+ }
+ delta_tm = mkgmtime(&opt_tm);
+
+ QUEUE_FOR_EACH(&track->waypoint_list, elem, temp) {
+ Waypoint* wpt = (Waypoint*)elem;
+
+ wpt->creation_time += delta_tm;
+ if ((prev != NULL) && (prev->creation_time > wpt->creation_time)) { /* go over midnight ? */
+ delta_tm += SECONDS_PER_DAY;
+ wpt->creation_time += SECONDS_PER_DAY;
+ }
+ prev = wpt;
+ }
+ } else {
+ time_t prev;
+ queue* elem;
+
+ tm.tm_hour = 23; /* last date found */
+ tm.tm_min = 59;
+ tm.tm_sec = 59;
+
+ prev = mkgmtime(&tm);
+
+ /* go backward through the track and complete timestamps */
+
+ for (elem = QUEUE_LAST(&track->waypoint_list); elem != &track->waypoint_list; elem=elem->prev) {
+ Waypoint* wpt = (Waypoint*)elem;
+
+ if (wpt->wpt_flags.fmt_use != 0) {
+ time_t dt;
+
+ wpt->wpt_flags.fmt_use = 0; /* reset flag */
+
+ dt = (prev / SECONDS_PER_DAY) * SECONDS_PER_DAY;
+ wpt->creation_time += dt;
+ if (wpt->creation_time.toTime_t() > prev) {
+ wpt->creation_time+=SECONDS_PER_DAY;
+ }
+ }
+ prev = wpt->GetCreationTime().toTime_t();
+ }
+ }
+}
+
+static int
+notalkerid_strmatch(const char * s1, const char *sentenceFormatterMnemonicCode)
+{
+/*
+ * compare leading start of parametric sentence character ('$'), sentence address field, and trailing comma
+ * to the desired sentence formatter mneumonic code (the 3rd-5th characters of the sentence address field).
+ * The talker identifier mneumonic (the 1st-2nd characters of the sentence address field)
+ * is likely "GP" for Global Posilioning System (GPS)
+ * but other talkers like "IN" for Integrated Navigation can emit relevant sentences,
+ * so we ignore the talker identifier mneumonic.
+ */
+return strncmp(s1,"$",1) || strncmp(s1+3,sentenceFormatterMnemonicCode,3) || strncmp(s1+6,",",1);
+}
+
+void
+nmea_parse_one_line(char* ibuf)
+{
+ char* ck;
+ int ckval, ckcmp;
+ char* tbuf = lrtrim(ibuf);
+
+ /*
+ * GISTEQ PhotoTracker (stupidly) puts a bogus field in front
+ * of the line. Look for it and toss it.
+ */
+ if (0 == strncmp(tbuf, "---,", 4)) {
+ tbuf += 4;
+ }
+
+ if (*tbuf != '$') {
+ return;
+ }
+
+ ck = strrchr(tbuf, '*');
+ if (ck != NULL) {
+ *ck = '\0';
+ ckval = nmea_cksum(&tbuf[1]);
+ *ck = '*';
+ ck++;
+ sscanf(ck, "%2X", &ckcmp);
+ if (ckval != ckcmp) {
+#if 0
+ printf("ckval %X, %X, %s\n", ckval, ckcmp, ck);
+ printf("NMEA %s\n", tbuf);
+#endif
+ return;
+ }
+
+ had_checksum = 1;
+ } else if (had_checksum) {
+ /* we have had a checksum on all previous sentences, but not on this
+ one, which probably indicates this line is truncated */
+ had_checksum = 0;
+ return;
+ }
+
+ if (strstr(tbuf+1,"$")!=NULL) {
+ /* If line has more than one $, there is probably an error in it. */
+ return;
+ }
+
+ /* @@@ zmarties: The parse routines all assume all fields are present, but
+ the NMEA format allows any field to be missed out if there is no data
+ for that field. Rather than change all the parse routines, we first
+ substitute a default value of zero for any missing field.
+ */
+ if (strstr(tbuf, ",,")) {
+ tbuf = gstrsub(tbuf, ",,", ",0,");
+ }
+
+ if (0 == notalkerid_strmatch(tbuf, "WPL")) {
+ gpwpl_parse(tbuf);
+ } else if (opt_gpgga && (0 == notalkerid_strmatch(tbuf, "GGA"))) {
+ posn_type = gpgga;
+ gpgga_parse(tbuf);
+ } else if (opt_gprmc && (0 == notalkerid_strmatch(tbuf, "RMC"))) {
+ if (posn_type != gpgga) {
+ posn_type = gprmc;
+ }
+ /*
+ * Always call gprmc_parse() because like GPZDA
+ * it contains the full date.
+ */
+ gprmc_parse(tbuf);
+ } else if (0 == notalkerid_strmatch(tbuf, "GLL")) {
+ if ((posn_type != gpgga) && (posn_type != gprmc)) {
+ gpgll_parse(tbuf);
+ }
+ } else if (0 == notalkerid_strmatch(tbuf, "ZDA")) {
+ gpzda_parse(tbuf);
+ } else if (0 == strncmp(tbuf, "$PCMPT,", 7)) {
+ pcmpt_parse(tbuf);
+ } else if (opt_gpvtg && (0 == notalkerid_strmatch(tbuf, "VTG"))) {
+ gpvtg_parse(tbuf); /* speed and course */
+ } else if (opt_gpgsa && (0 == notalkerid_strmatch(tbuf, "GSA"))) {
+ gpgsa_parse(tbuf); /* GPS fix */
+ } else if (0 == strncmp(tbuf, "$ADPMB,5,0", 10)) {
+ amod_waypoint = 1;
+ }
+
+ if (tbuf != ibuf) {
+ /* clear up the dynamic buffer we used because substition was required */
+ xfree(tbuf);
+ }
+}
+
+static void
+nmea_read(void)
+{
+ char* ibuf;
+ char* ck;
+ double lt = -1;
+ int line = -1;
+
+ posn_type = gp_unknown;
+ trk_head = NULL;
+ without_date = 0;
+ memset(&tm, 0, sizeof(tm));
+ opt_tm = tm;
+
+ /* This was done in rd_init() */
+ if (getposn) {
+ return;
+ }
+
+ if (optdate) {
+ memset(&opt_tm, 0, sizeof(opt_tm));
+
+ ck = (char*)strptime(optdate, "%Y%m%d", &opt_tm);
+ if ((ck == NULL) || (*ck != '\0') || (strlen(optdate) != 8)) {
+ fatal(MYNAME ": Invalid date \"%s\"!\n", optdate);
+ } else if (opt_tm.tm_year < 70) {
+ fatal(MYNAME ": Date \"%s\" is out of range (have to be 19700101 or later)!\n", optdate);
+ }
+ }
+
+ curr_waypt = NULL;
+
+ while ((ibuf = gbfgetstr(file_in))) {
+ char* sdatum, *cx;
+
+ line++;
+
+ if ((line == 0) & file_in->unicode) {
+ cet_convert_init(CET_CHARSET_UTF8, 1);
+ }
+
+ if ((line == 0) && (case_ignore_strncmp(ibuf, "@SonyGPS/ver", 12) == 0)) {
+ /* special hack for Sony GPS-CS1 files:
+ they are fully (?) nmea compatible, but come with a header line like
+ "@Sonygps/ver1.0/wgs-84". */
+ /* The Sony GPS-CS3KA extends that line even further
+ so we now look for the second field to be /
+ delimited.
+ @Sonygps/ver1.0/wgs-84/gps-cs3.0
+ */
+
+ /* Check the GPS datum */
+ cx = strchr(&ibuf[12], '/');
+ if (cx != NULL) {
+ char* edatum;
+ sdatum = cx + 1;
+ edatum = strchr(sdatum, '/');
+ if (edatum) {
+ *edatum = 0;
+ }
+ datum = GPS_Lookup_Datum_Index(sdatum);
+ if (datum < 0) {
+ fatal(MYNAME "/SonyGPS: Unsupported datum \"%s\" in source data!\n", sdatum);
+ }
+ }
+ continue;
+ }
+
+ nmea_parse_one_line(ibuf);
+ if (lt != last_read_time && curr_waypt && trk_head) {
+ if (curr_waypt != last_waypt) {
+ nmea_add_wpt(curr_waypt, trk_head);
+ last_waypt = curr_waypt;
+ }
+ lt = last_read_time;
+ }
+ }
+
+ /* try to complete date-less trackpoints */
+ nmea_fix_timestamps(trk_head);
+}
+
+void
+nmea_rd_posn_init(const QString& fname)
+{
+ if ((gbser_handle = gbser_init(qPrintable(fname))) != NULL) {
+ read_mode = rm_serial;
+ gbser_set_speed(gbser_handle, 4800);
+ } else {
+ fatal(MYNAME ": Could not open '%s' for position tracking.\n", qPrintable(fname));
+ }
+
+ gbser_flush(gbser_handle);
+
+ if (opt_baud) {
+ if (!gbser_set_speed(gbser_handle, atoi(opt_baud))) {
+ fatal(MYNAME ": Unable to set baud rate %s\n", opt_baud);
+ }
+ }
+ posn_fname = fname;
+}
+
+static void
+safe_print(int cnt, const char* b)
+{
+ int i;
+ for (i = 0; i < cnt; i++) {
+ char c = isprint(b[i]) ? b[i] : '.';
+ fputc(c, stderr);
+ }
+}
+
+static void reset_sirf_to_nmea(int br);
+
+static
+int hunt_sirf(void)
+{
+ /* Try to place the common BR's first to speed searching */
+ static int br[] = {38400, 9600, 57600, 115200, 19200, 4800, -1};
+ static int* brp = &br[0];
+ char ibuf[1024];
+
+ for (brp = br; *brp > 0; brp++) {
+ int rv;
+ if (global_opts.debug_level > 1) {
+ fprintf(stderr, "Trying %d\n", *brp);
+ }
+
+ /*
+ * Cycle our port's data speed and spray the "change to NMEA
+ * mode to the device.
+ */
+ gbser_set_speed(gbser_handle, *brp);
+ reset_sirf_to_nmea(*brp);
+
+ rv = gbser_read_line(gbser_handle, ibuf, sizeof(ibuf),
+ 1000, 0x0a, 0x0d);
+ /*
+ * If we didn't get a read error but did get a string that
+ * started with a dollar sign, we're probably in NMEA mode
+ * now.
+ */
+ if ((rv > -1) && (strlen(ibuf) > 0) && ibuf[0] == '$') {
+ return 1;
+ }
+
+ /*
+ * If nothing was received, it's not a sirf part. Fast exit.
+ */
+ if (rv < 0) {
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static Waypoint*
+nmea_rd_posn(posn_status* posn_status)
+{
+ char ibuf[1024];
+ static double lt = -1;
+ int i;
+ int am_sirf = 0;
+
+ /*
+ * Read a handful of sentences, collecting the best info we
+ * can. If the timestamp changes (indicating the sequence is
+ * about to restart and thus the one we're collecting isn't going
+ * to get any better than we now have) hand that back to the caller.
+ */
+
+ for (i = 0; i < 10; i++) {
+ int rv;
+ ibuf[0] = 0;
+ rv = gbser_read_line(gbser_handle, ibuf, sizeof(ibuf), 2000, 0x0a, 0x0d);
+ if (global_opts.debug_level > 1) {
+ safe_print(strlen(ibuf), ibuf);
+ }
+ if (rv < 0) {
+ if (am_sirf == 0) {
+ if (global_opts.debug_level > 1) {
+ warning(MYNAME ": Attempting sirf mode.\n");
+ }
+ /* This is tacky, we have to change speed
+ * to 9600bps to tell it to speak NMEA at
+ * 4800.
+ */
+ am_sirf = hunt_sirf();
+ if (am_sirf) {
+ i = 0;
+ continue;
+ }
+ }
+ fatal(MYNAME ": No data received on %s.\n", qPrintable(posn_fname));
+ }
+ nmea_parse_one_line(ibuf);
+ if (lt != last_read_time) {
+ if (last_read_time) {
+ Waypoint* w = curr_waypt;
+
+ lt = last_read_time;
+ curr_waypt = NULL;
+
+ return w;
+ }
+ }
+ }
+ return NULL;
+}
+
+static void
+nmea_wayptpr(const Waypoint* wpt)
+{
+ char obuf[200];
+ double lat,lon;
+ QString s;
+ int cksum;
+
+ lat = degrees2ddmm(wpt->latitude);
+ lon = degrees2ddmm(wpt->longitude);
+ if (global_opts.synthesize_shortnames) {
+ s = mkshort_from_wpt(mkshort_handle, wpt);
+ } else {
+ s = mkshort(mkshort_handle, wpt->shortname);
+ }
+
+ snprintf(obuf, sizeof(obuf), "GPWPL,%08.3f,%c,%09.3f,%c,%s",
+ fabs(lat), lat < 0 ? 'S' : 'N',
+ fabs(lon), lon < 0 ? 'W' : 'E', CSTRc(s)
+ );
+ cksum = nmea_cksum(obuf);
+ gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+ if (sleepus >= 0) {
+ gbfflush(file_out);
+ gb_sleep(sleepus);
+ }
+}
+void
+nmea_track_init(const route_head*)
+{
+ last_time = -1;
+}
+
+void
+nmea_trackpt_pr(const Waypoint* wpt)
+{
+ char obuf[200];
+ char fix='0';
+ double lat,lon;
+ int cksum;
+ struct tm* tm;
+ time_t hms;
+ time_t ymd;
+
+ if (opt_sleep) {
+ gbfflush(file_out);
+ if (last_time > 0) {
+ if (sleepus >= 0) {
+ gb_sleep(sleepus);
+ } else {
+ long wait_time = wpt->GetCreationTime().toTime_t() - last_time;
+ if (wait_time > 0) {
+ gb_sleep(wait_time * 1000000);
+ }
+ }
+ }
+ last_time = wpt->GetCreationTime().toTime_t();
+ }
+
+ lat = degrees2ddmm(wpt->latitude);
+ lon = degrees2ddmm(wpt->longitude);
+
+ time_t ct = wpt->GetCreationTime().toTime_t();
+ tm = gmtime(&ct);
+ if (tm) {
+ hms = tm->tm_hour * 10000 + tm->tm_min * 100 + tm->tm_sec;
+ ymd = tm->tm_mday * 10000 + tm->tm_mon * 100 + tm->tm_year;
+ } else {
+ hms = 0;
+ ymd = 0;
+ }
+
+ switch (wpt->fix) {
+ case fix_dgps:
+ fix='2';
+ break;
+ case fix_3d:
+ case fix_2d:
+ fix='1';
+ break;
+ case fix_pps:
+ fix='3';
+ break;
+ default:
+ fix='0';
+ }
+
+ if (opt_gprmc) {
+ snprintf(obuf, sizeof(obuf), "GPRMC,%010.3f,%c,%08.3f,%c,%09.3f,%c,%.2f,%.2f,%06d,,",
+ (double) hms + (wpt->GetCreationTime().time().msec() / 1000.0),
+ fix=='0' ? 'V' : 'A',
+ fabs(lat), lat < 0 ? 'S' : 'N',
+ fabs(lon), lon < 0 ? 'W' : 'E',
+ WAYPT_HAS(wpt, speed) ? MPS_TO_KNOTS(wpt->speed):(0),
+ WAYPT_HAS(wpt, course) ? (wpt->course):(0),
+ (int) ymd);
+ cksum = nmea_cksum(obuf);
+
+ /* GISTeq doesn't care about the checksum, but wants this prefixed, so
+ * we can write it with abandon.
+ */
+ if (opt_gisteq) {
+ gbfprintf(file_out, "---,");
+ }
+ gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+ }
+ if (opt_gpgga) {
+ snprintf(obuf, sizeof(obuf), "GPGGA,%010.3f,%08.3f,%c,%09.3f,%c,%c,%02d,%.1f,%.3f,M,%.1f,M,,",
+ (double) hms + (wpt->GetCreationTime().time().msec() / 1000.0),
+ fabs(lat), lat < 0 ? 'S' : 'N',
+ fabs(lon), lon < 0 ? 'W' : 'E',
+ fix,
+ (wpt->sat>0)?(wpt->sat):(0),
+ (wpt->hdop>0)?(wpt->hdop):(0.0),
+ wpt->altitude == unknown_alt ? 0 : wpt->altitude,
+ WAYPT_HAS(wpt, geoidheight)? (wpt->geoidheight) : (0)); /* TODO: we could look up the geoidheight if needed */
+ cksum = nmea_cksum(obuf);
+ gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+ }
+ if ((opt_gpvtg) && (WAYPT_HAS(wpt, course) || WAYPT_HAS(wpt, speed))) {
+ snprintf(obuf,sizeof(obuf),"GPVTG,%.3f,T,0,M,%.3f,N,%.3f,K",
+ WAYPT_HAS(wpt, course) ? (wpt->course):(0),
+ WAYPT_HAS(wpt, speed) ? MPS_TO_KNOTS(wpt->speed):(0),
+ WAYPT_HAS(wpt, speed) ? MPS_TO_KPH(wpt->speed):(0));
+
+ cksum = nmea_cksum(obuf);
+ gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+ }
+
+ if ((opt_gpgsa) && (wpt->fix!=fix_unknown)) {
+
+ switch (wpt->fix) {
+ case fix_dgps:
+ /* or */
+ case fix_3d:
+ fix='3';
+ break;
+ case fix_2d:
+ fix='2';
+ break;
+ default:
+ fix=0;
+ }
+ snprintf(obuf,sizeof(obuf),"GPGSA,A,%c,,,,,,,,,,,,,%.1f,%.1f,%.1f",
+ fix,
+ (wpt->pdop>0)?(wpt->pdop):(0),
+ (wpt->hdop>0)?(wpt->hdop):(0),
+ (wpt->vdop>0)?(wpt->vdop):(0));
+ cksum = nmea_cksum(obuf);
+ gbfprintf(file_out, "$%s*%02X\n", obuf, cksum);
+ }
+ gbfflush(file_out);
+}
+
+static void
+nmea_write(void)
+{
+ waypt_disp_all(nmea_wayptpr);
+ track_disp_all(nmea_track_init, NULL, nmea_trackpt_pr);
+}
+
+static void
+nmea_wr_posn_init(const QString& fname)
+{
+ nmea_wr_init(fname);
+}
+
+static void
+nmea_wr_posn(Waypoint* wpt)
+{
+ nmea_trackpt_pr(wpt);
+}
+
+static void
+nmea_wr_posn_deinit(void)
+{
+// nmea_wr_deinit();
+}
+
+
+ff_vecs_t nmea_vecs = {
+ ff_type_file,
+ {
+ (ff_cap)(ff_cap_read | ff_cap_write),
+ (ff_cap)(ff_cap_read | ff_cap_write),
+ ff_cap_none
+ },
+ nmea_rd_init,
+ nmea_wr_init,
+ nmea_rd_deinit,
+ nmea_wr_deinit,
+ nmea_read,
+ nmea_write,
+ NULL,
+ nmea_args,
+ CET_CHARSET_ASCII, 0, /* CET-REVIEW */
+ {
+ nmea_rd_posn_init, nmea_rd_posn, nmea_rd_deinit,
+ nmea_wr_posn_init, nmea_wr_posn, nmea_wr_posn_deinit
+ }
+};
+
+/*
+ * If we later decide to implement a "real" Sirf module, this code should
+ * go there. For now, we try a kind of heavy handed thing - if we don't
+ * see NMEA-isms from the device, we'll go on the premise that it MAY be
+ * a SiRF Star device and send it the "speak NMEA, please" command.
+ */
+
+static void
+sirf_write(unsigned char* buf)
+{
+ int i, chksum = 0;
+ int len = buf[2] << 8 | buf[3];
+
+ for (i = 0; i < len; i++) {
+ chksum += buf[4 + i];
+ }
+ chksum &= 0x7fff;
+
+ buf[len + 4] = chksum >> 8;
+ buf[len + 5] = chksum & 0xff;
+
+ gbser_write(gbser_handle, buf, len + 8); /* 4 at front, 4 at back */
+}
+
+static
+void reset_sirf_to_nmea(int br)
+{
+ static unsigned char pkt[] = {0xa0, 0xa2, 0x00, 0x18,
+ 0x81, 0x02,
+ 0x01, 0x01, /* GGA */
+ 0x00, 0x00, /* suppress GLL */
+ 0x01, 0x00, /* suppress GSA */
+ 0x05, 0x00, /* suppress GSV */
+ 0x01, 0x01, /* use RMC for date*/
+ 0x00, 0x00, /* suppress VTG */
+ 0x00, 0x01, /* output rate */
+ 0x00, 0x01, /* unused recommended values */
+ 0x00, 0x01,
+ 0x00, 0x01, /* ZDA */
+ 0x12, 0xc0, /* 4800 bps */
+ 0x00, 0x00, /* checksum */
+ 0xb0, 0xb3
+ }; /* packet end */
+ /* repopulate bit rate */
+ pkt[26] = br >> 8;
+ pkt[27] = br & 0xff;
+
+ sirf_write(pkt);
+ gb_sleep(250 * 1000);
+ gbser_flush(gbser_handle);
+}